using System.Net.Sockets;

namespace Pinwheel.Kernel
{
    public class SocketSession : ISocketSession
    {
        protected ISocket m_Socket;
        
        protected SocketAsyncEventArgs m_ReceiveEventArgs;
        protected SocketBuffer m_Received;
        
        protected SocketAsyncEventArgs m_SendEventArgs;
        protected SocketBuffer m_Prepare;
        protected int m_State;
        
        protected ISocketListener m_Listener;
        public ISocketListener Listener => m_Listener;
        
        public ISocket Socket
        {
            get => m_Socket;
            set => m_Socket = value;
        }
        
        public SocketAsyncEventArgs ReceiveEventArgs
        {
            get => m_ReceiveEventArgs;
            set => m_ReceiveEventArgs = value;
        }
        
        public SocketBuffer Received
        {
            get => m_Received;
            set => m_Received = value;
        }
        
        public SocketAsyncEventArgs SendEventArgs
        {
            get => m_SendEventArgs;
            set => m_SendEventArgs = value;
        }
        
        public SocketBuffer Prepare
        {
            get => m_Prepare;
            set => m_Prepare = value;
        }

        public SocketSession(ISocket socket, ISocketListener listener, SocketAsyncEventArgs receiveEventArgs,
            SocketAsyncEventArgs sendEventArgs, int bufferSize = SocketBuffer.DefaultSize) : this(bufferSize)
        {
            m_Socket = socket;
            m_Listener = listener;
            m_ReceiveEventArgs = receiveEventArgs;
            m_SendEventArgs = sendEventArgs;
            
            m_ReceiveEventArgs.UserToken = this;
            m_SendEventArgs.UserToken = this;
        }
        
        public SocketSession(int bufferSize = SocketBuffer.DefaultSize)
        {
            m_Received = new SocketBuffer(bufferSize);
            m_Prepare = new SocketBuffer(bufferSize);
        }
        
        public void Accept(Socket socket)
        {
            // Log.Info($"[SocketSession] Client {socket.RemoteEndPoint} connected.");
            SendEventArgs.AcceptSocket = socket;
            SendEventArgs.RemoteEndPoint = socket.RemoteEndPoint;
            ReceiveEventArgs.AcceptSocket = socket;
            ReceiveEventArgs.RemoteEndPoint = socket.RemoteEndPoint;
            m_Listener.OnConnected(this);
        }

        public void Connect(Socket socket)
        {
            // Log.Info($"[SocketSession] Connect to server {socket.RemoteEndPoint} succeed.");
            SendEventArgs.AcceptSocket = socket;
            SendEventArgs.RemoteEndPoint = socket.RemoteEndPoint;
            ReceiveEventArgs.AcceptSocket = socket;
            ReceiveEventArgs.RemoteEndPoint = socket.RemoteEndPoint;
            m_Listener.OnConnected(this);
        }

        public void ConnectFailed()
        {
            // Log.Info($"[SocketSession] {ReceiveEventArgs.RemoteEndPoint} connection failed.");
            m_Listener.OnConnectFailed(this);
        }

        public void Receive(Socket socket)
        {
            Assert.Check(null != m_ReceiveEventArgs, "Try receive data with null event args.");
            
            var size = m_ReceiveEventArgs.BytesTransferred;
            Assert.Check(size > 0, $"Try receive data with size zero.");
            
            // Log.Info($"[SocketSession] Receive from {socket.RemoteEndPoint} {size} byte data.");
            
            m_Received.Skip(size);
            m_ReceiveEventArgs.SetBuffer(m_Received.Tail, m_Received.WriteCount);
            
            m_Listener.OnReceive(this);
        }

        public void Send(byte[] data, int index = 0)
        {
            Assert.Check(null != m_SendEventArgs, "Try send data with null event args.");
            var value = Interlocked.CompareExchange(ref m_State, 1, 0);

            if (m_Listener is ISocketSendListener sender)
                sender.OnSend(this, ref data); 
            
            m_Prepare.Push(data, index);
            if (value > 0) return;
            
            // Log.Info($"[SocketSession] Send to {m_SendEventArgs.RemoteEndPoint} {m_Prepare.ReadCount} byte data.");
            m_SendEventArgs.SetBuffer(m_Prepare.Buffer, m_Prepare.Head, m_Prepare.ReadCount);
            m_Socket.StartSend(m_SendEventArgs); 
        }
        
        public void Sended(int size)
        {
            m_Prepare.Clear(size);
            if (m_Prepare.ReadCount > 0)
            {
                // Log.Info($"[SocketSession] Send to {m_SendEventArgs.RemoteEndPoint} {m_Prepare.ReadCount} byte data.");
                m_SendEventArgs.SetBuffer(m_Prepare.Buffer, m_Prepare.Head, m_Prepare.ReadCount); 
            }
            else
            {
                Interlocked.CompareExchange(ref m_State, 0, 1); 
            }
        }

        public void Error(Socket socket, SocketError error)
        {
            if (null! != socket)
                Log.Info($"[SocketSession] {socket.RemoteEndPoint} error {error}.");
            m_Listener.OnError(this, error);
        }
        
        public void Close(Socket socket)
        {
            if (null! != socket) 
                Log.Info($"[SocketSession] {socket.RemoteEndPoint} connection closed.");
            m_Listener.OnClose(this);
            SendEventArgs.AcceptSocket = null;
            SendEventArgs.RemoteEndPoint = null;
            ReceiveEventArgs.AcceptSocket = null;
            ReceiveEventArgs.RemoteEndPoint = null;
        }
    }
}